package com.uppfind.framework.dao.mybatis.builder;

import com.uppfind.framework.core.exception.BusinessException;
import com.uppfind.framework.dao.api.common.Sort;
import com.uppfind.framework.dao.api.common.Term;
import com.uppfind.framework.dao.api.common.params.DeleteParam;
import com.uppfind.framework.dao.api.common.params.InsertParam;
import com.uppfind.framework.dao.api.common.params.QueryParam;
import com.uppfind.framework.dao.api.common.params.UpdateParam;
import com.uppfind.framework.dao.mybatis.meta.ColumnMetaData;
import com.uppfind.framework.dao.mybatis.meta.TableMetaData;
import com.uppfind.framework.dao.mybatis.utils.ResultMapsUtils;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;

import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.uppfind.framework.utils.sql.AssemblerGetAndSetMethodUtil.getMethod;

/**
 * Created by xuliugen on 2017/6/29.
 */
public class DefaultSqlBuilder {

    private static final DefaultSqlBuilder instance = new DefaultSqlBuilder();
    private static final String DEFAULT_SELECT_ALL = " * ";
    private static final String DEFAULT_WHERE_SPACE = " ";
    private static final String DEFAULT_WHERE_EQUAL = " 1=1 ";
    private static final String DEFAULT_ORDER_BY_COMMA = ",";
    private static final String ORDER_BY_PREFIX = " order by ";
    /**
     * 表和表属性
     */
    private final ConcurrentMap<String, TableMetaData> tableMetaDataCache = new ConcurrentHashMap<String, TableMetaData>() {
        @Override
        public TableMetaData get(Object key) {
            return super.get(key);
        }
    };

    private DefaultSqlBuilder() {
    }

    public static DefaultSqlBuilder getInstance() {
        return instance;
    }

    /**
     * 创建TableMetaData对象
     * @param tableName   表名称
     * @param resultMapId MyBatis种MapperXML的resultMap的Id
     * @return
     */
    protected TableMetaData createTableMetaData(String tableName, String resultMapId) {
        String cacheKey = tableName.concat("-").concat(resultMapId);
        TableMetaData cached = this.tableMetaDataCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }
        TableMetaData tableMetaData = new TableMetaData();
        tableMetaData.setName(tableName);

        ResultMap resultMaps = ResultMapsUtils.getResultMap(resultMapId);
        List<ResultMapping> resultMappings = new ArrayList<>(resultMaps.getResultMappings());
        resultMappings.addAll(resultMaps.getIdResultMappings());
        resultMappings.forEach(resultMapping -> {
            if (resultMapping.getNestedQueryId() == null) { //内查询ID为空
                ColumnMetaData column = new ColumnMetaData();
                column.setJdbcType(JDBCType.valueOf(resultMapping.getJdbcType().name()));
                column.setName(resultMapping.getColumn());
                column.setProperty(resultMapping.getProperty());
                column.setJavaType(resultMapping.getJavaType());
                if (isPrimaryId(resultMapping)) { //主键ID
                    column.setPrimaryKey(true);
                }
                tableMetaData.addColumn(column);
            }
        });
        //加入缓存
        tableMetaDataCache.put(cacheKey, tableMetaData);
        return tableMetaData;
    }

    public Boolean isPrimaryId(ResultMapping resultMapping) {
        List<ResultFlag> resultFlags = resultMapping.getFlags();
        for (int i = 0; i < resultFlags.size(); i++) {
            if (resultFlags.get(i).equals(ResultFlag.ID)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 插入单条数据
     * @param tableName
     * @param resultMapId
     * @param param
     * @return
     * @throws Exception
     */
    public String buildInsertSelectiveSql(String tableName, String resultMapId, InsertParam param) throws Exception {
        if (null == param.getPo()) {
            throw new NullPointerException("不能插入为null的数据!");
        } else {
            Class clazz = param.getPOType();
            StringBuffer result = new StringBuffer("insert into " + tableName + "(");
            StringBuffer sqlFiled = new StringBuffer("");
            StringBuffer sqlValues = new StringBuffer("");
            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);

            tableMetaData.getColumnMetaDataMap().forEach((property, columnMetaData) -> {
                try {
                    Object propertyValue = clazz.getMethod(getMethod(columnMetaData.getProperty())).invoke(param.getPo());
                    String javaType = columnMetaData.getJavaType().toString().substring(6, columnMetaData.getJavaType().toString().length());
                    if (null != propertyValue) {
                        sqlFiled.append(columnMetaData.getName() + DEFAULT_ORDER_BY_COMMA);
                        sqlValues.append("#{ po." + columnMetaData.getProperty() + ",javaType=" + javaType + ",jdbcType=" + columnMetaData.getJdbcType().getName() + "},");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            result.append(sqlFiled.toString().substring(0, sqlFiled.toString().length() - 2));
            result.append(") values(");
            result.append(sqlValues.toString().substring(0, sqlValues.toString().length() - 1));
            result.append(");");

            return result.toString();
        }
    }

    /**
     * 更新单条数据
     * @param tableName
     * @param resultMapId
     * @param param
     * @return
     * @throws Exception
     */
    public String buildUpdateSelectiveSql(String tableName, String resultMapId, UpdateParam param) throws Exception {
        if (null == param.getPo()) {
            throw new BusinessException("禁止执行无条件的更新操作");
        } else {
            Class clazz = param.getPOType();
            StringBuffer result = new StringBuffer("update " + tableName + " set ");
            StringBuffer sql = new StringBuffer("");
            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);

            //用于存放主键ID相关的信息
            List<Object> idParam = new ArrayList<>(4);

            tableMetaData.getColumnMetaDataMap().forEach((property, columnMetaData) -> {
                try {
                    Object propertyValue = clazz.getMethod(getMethod(columnMetaData.getProperty())).invoke(param.getPo());
                    String javaType = columnMetaData.getJavaType().toString().substring(6, columnMetaData.getJavaType().toString().length());
                    if (null != propertyValue) {
                        sql.append(columnMetaData.getName() + "=#{po." + columnMetaData.getProperty() + ",javaType=" + javaType + ",jdbcType=" + columnMetaData.getJdbcType() + "},");
                    }
                    if (columnMetaData.isPrimaryKey()) { //如果是主键ID
                        idParam.add(columnMetaData.getName()); //主键column
                        idParam.add(columnMetaData.getProperty()); //主键属性
                        idParam.add(javaType);  //主键JavaType
                        idParam.add(columnMetaData.getJdbcType().getName());  //主键JdbcType
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            result.append(sql.toString().substring(0, sql.toString().length() - 1).toString());
            if (idParam.size() < 0 && param.getTerms().isEmpty()) {
                result.append(" where 1=2");
                throw new BusinessException("不允许更新条件为空的更新操作！");
            } else {
                result.append(" where 1=1 ");
                Object idValue = clazz.getMethod(getMethod(idParam.get(1).toString())).invoke(param.getPo());
                if (idParam.size() > 0 && null != idValue) {
                    result.append(" and " + idParam.get(0) + "=#{po." + idParam.get(1) + ",javaType=" + idParam.get(2) + ",jdbcType=" + idParam.get(3) + "}");
                } else { // 如果更新的ID 不为空的话，就不需要构造其他where条件，如果ID为空的话，就需要构造其他的where条件
                    if (!param.getTerms().isEmpty()) {
                        StringBuilder sqlWhere = new StringBuilder();
                        sqlWhere.append(buildWhereForUpdate(tableName, resultMapId, param));
                        result.append(" and ").append(sqlWhere);
                    }
                }
            }
            return result.toString();
        }
    }

    /**
     * 构造更新的where条件，不包含主键条件
     * @param tableName
     * @param resultMapId
     * @param param
     * @return
     * @throws Exception
     */
    public String buildWhereForUpdate(String tableName, String resultMapId, UpdateParam param) throws Exception {
        QueryParam queryParam = new QueryParam();
        queryParam.setTerms(param.getTerms());
        queryParam.setForUpdate(true);
        return buildWhere(tableName, resultMapId, queryParam);
    }

    /**
     * 构造select查询的属性
     * @param tableName
     * @param resultMapId
     * @param param       查询传入的参数
     * @return
     */
    public String buildSelectFields(String tableName, String resultMapId, QueryParam param) throws Exception {

        //不需要进行处理的属性
        Set<String> excludes = param.getExcludes();

        //exclude的属性为空的时候，直接返回"*"
        if (excludes.isEmpty()) {
            return DEFAULT_SELECT_ALL;
        } else {
            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);
            Set<String> properties = new HashSet<>();
            tableMetaData.getColumnMetaDataMap().forEach((property, columnMetaData) -> {
                properties.add(columnMetaData.getProperty()); //获取数据库表中所有的属性
            });
            excludes.forEach(exclude -> {
                if (properties.contains(exclude)) { //如果包含该属性
                    properties.remove(exclude);
                } else {
                    throw new BusinessException("参数excludes中属性：" + exclude + "设置不正确，请检查！");
                }
            });
            StringBuilder sb = new StringBuilder();
            StringBuilder result = new StringBuilder();
            if (properties.isEmpty()) { //全部排除了属性
                return DEFAULT_SELECT_ALL;
            } else {
                properties.forEach(property -> {
                    sb.append(DEFAULT_WHERE_SPACE + tableMetaData.getColumnMetaDataMap().get(property).getName() + DEFAULT_ORDER_BY_COMMA);
                });
                result.append(sb.toString().substring(0, sb.toString().length() - 1));
                result.append(DEFAULT_WHERE_SPACE);
            }
            return result.toString();
        }
    }

    /**
     * 构造查询where条件，包含分页和不分页
     * @param tableName
     * @param resultMapId
     * @param param
     * @return
     * @throws Exception
     */
    public String buildWhere(String tableName, String resultMapId, QueryParam param) throws Exception {
        if (param.isPaging() && !param.isForUpdate() && !param.isForTotal()) {
            //起始游标位置
            long start = param.getStart();
            //页大小
            int pageSize = param.getPageSize();

            StringBuilder sb = new StringBuilder(getWhereSql(tableName, resultMapId, param));
            sb.append(" limit " + start + " , " + pageSize);
            return sb.toString();
        } else if (!param.isPaging() || param.isForUpdate() || param.isForTotal()) {
            return getWhereSql(tableName, resultMapId, param);
        } else {
            return " 1==2 ";
        }
    }

    /**
     * 构造分页和不分页的通用查询sql
     * @param tableName
     * @param resultMapId
     * @param param
     * @return
     */
    public String getWhereSql(String tableName, String resultMapId, QueryParam param) {
        List<Term> terms = param.getTerms();
        if (terms.isEmpty()) {
            return DEFAULT_WHERE_EQUAL;
        } else {
            StringBuffer sb = new StringBuffer();
            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);
            for (int i = 0; i < terms.size(); i++) {
                ColumnMetaData columnMetaData = tableMetaData.getColumnMetaDataMap().get(terms.get(i).getProperty());
                if (null == columnMetaData) {
                    throw new BusinessException("查询参数param中属性：" + terms.get(i).getProperty() + "设置不正确，请检查！");
                } else {
                    String javaType = columnMetaData.getJavaType().toString().substring(6, columnMetaData.getJavaType().toString().length());
                    sb.append(columnMetaData.getName() + DEFAULT_WHERE_SPACE + terms.get(i).getLinkType() + "#{terms[" + i + "].value,javaType=" + javaType + ",jdbcType=" + columnMetaData.getJdbcType() + "}" + DEFAULT_WHERE_SPACE);
                    if (i != terms.size() - 1) {  //不是最后一个查询条件
                        sb.append(terms.get(i).getJoinType() + DEFAULT_WHERE_SPACE);
                    }
                }
            }
            StringBuilder result = new StringBuilder();
            result.append(DEFAULT_WHERE_SPACE + sb.toString().substring(0, sb.toString().length() - 1) + DEFAULT_WHERE_SPACE);
            return result.toString();
        }
    }

    public String buildOrder(String tableName, String resultMapId, QueryParam param) throws Exception {
        List<Sort> sorts = param.getSorts();
        if (sorts.isEmpty()) {
            return DEFAULT_WHERE_SPACE;
        } else {
            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);
            StringBuffer sb = new StringBuffer(ORDER_BY_PREFIX);
            for (int i = 0; i < sorts.size(); i++) {
                ColumnMetaData columnMetaData = tableMetaData.getColumnMetaDataMap().get(sorts.get(i).getProperty());
                if (null == columnMetaData) {
                    throw new BusinessException("排序参数sorts中属性：" + sorts.get(i).getProperty() + "设置不正确，请检查！");
                } else {
                    sb.append(columnMetaData.getName() + DEFAULT_WHERE_SPACE + sorts.get(i).getOrder() + DEFAULT_ORDER_BY_COMMA);
                }
            }
            StringBuilder result = new StringBuilder();
            result.append(sb.toString().substring(0, sb.toString().length() - 1));
            return result.toString();
        }
    }

    public String buildIdsForDelete(String tableName, String resultMapId, DeleteParam param) throws Exception {
        if (param.getDeleteIds().isEmpty()) {
            throw new BusinessException("禁止执行无条件的更新操作");
        } else {
            //用于存放主键ID相关的信息
            List<Object> idParam = new ArrayList<>(2);

            TableMetaData tableMetaData = createTableMetaData(tableName, resultMapId);
            tableMetaData.getColumnMetaDataMap().forEach((property, columnMetaData) -> {
                if (columnMetaData.isPrimaryKey()) {
                    idParam.add(columnMetaData.getJavaType()); //主键JavaType
                    idParam.add(columnMetaData.getJdbcType()); //主键JdbcType
                    idParam.add(columnMetaData.getName()); //主键column
                }
            });
            StringBuffer sb = new StringBuffer(idParam.get(2) + " in(");
            param.getDeleteIds().forEach(id -> {
                try {
                    if (id.getClass().newInstance() instanceof String) { //如果是String
                        sb.append("'" + id + "'" + DEFAULT_ORDER_BY_COMMA);
                    } else if (id.getClass().newInstance() instanceof Integer) { //如果是整数
                        sb.append(id + DEFAULT_ORDER_BY_COMMA);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            StringBuilder result = new StringBuilder();
            result.append(sb.toString().substring(0, sb.length() - 1));
            result.append(")");
            return result.toString();
        }
    }

}
